package de.jmda.sample.junit;

import static de.jmda.core.mproc.ProcessingUtilities.getElementsAnnotatedWith;
import static de.jmda.core.mproc.ProcessingUtilities.isProcessingOver;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;

import org.apache.log4j.Logger;
import org.junit.Test;

import de.jmda.core.mproc.LaunchConfiguration;
import de.jmda.core.mproc.Launcher;
import de.jmda.core.mproc.Processor;
import de.jmda.core.util.fileset.FileSetJavaFilesOnly;
import de.jmda.core.util.fileset.FileSet.RuleSetUpdateMode;

/**
 * Processes @{@link Test} annotated method elements. Determines their types and
 * stores them in {@link #typeElements}.
 *
 * @author roger.jmda@gmail.com
 */
@SupportedAnnotationTypes(value = {"org.junit.Test"})
@SupportedSourceVersion(value = SourceVersion.RELEASE_7)
public class TestProcessor
		extends Processor
{
	private final static Logger LOGGER =
      Logger.getLogger(TestProcessor.class);

	/**
	 * stores type elements that have at least one method annotated with @{@link
	 * Test}
	 */
	private Set<TypeElement> typeElements = new HashSet<TypeElement>();

	/**
	 * Determines types of all @{@link Test} annotated methods and stores them in
	 * {@link #typeElements}.
	 *
	 * @see de.jmda.core.mproc.Processor#process()
	 */
	@Override
	protected boolean process()
	{
		if (isProcessingOver() == false)
		{
			for (Element element :
			     getElementsAnnotatedWith(Test.class))
			{
				if (element instanceof ExecutableElement)
				{
					ExecutableElement executableElement = (ExecutableElement) element;
					
					Element enclosingElement = executableElement.getEnclosingElement();
					
					if (enclosingElement instanceof TypeElement)
					{
						typeElements.add((TypeElement) enclosingElement);
					}
					else
					{
						LOGGER.warn(
								"unexpected enclosing type for executable element: " +
								enclosingElement.getClass().getName());
					}
				}
				else
				{
					LOGGER.warn(
							"unexpected element type with " + Test.class.getName() +
							" annotation: " + element.toString());
				}
			}
		}
		return false;
	}


	@Override
	protected Set<Class<? extends Annotation>> getSupposedSupportedAnnotationTypes()
	{
		return asSet(Test.class);
	}

	/**
	 * Reads all types in directory <code>sourceRoot</code> and its subdirectories
	 * and returns a set containing type elements for each type that has at least
	 * one @{@link Test} annotated method.
	 *
	 * @param sourceRoot
	 * @return see above
	 */
	public static Set<TypeElement> process(File sourceRoot)
	{
		// define the set of files to process
		FileSetJavaFilesOnly fileSetJavaFilesOnly = new FileSetJavaFilesOnly();

		fileSetJavaFilesOnly.includeDirectoryRecursive(
				sourceRoot, RuleSetUpdateMode.ADD_RULE);
		
		// declare the processor
		TestProcessor testProcessor = new TestProcessor();
		
		// define the launch configuration using the file set and the processor
		LaunchConfiguration launchConfiguration = new LaunchConfiguration();

		launchConfiguration.files.addAll(fileSetJavaFilesOnly.calculateFileSet());
		launchConfiguration.processors.add(testProcessor);

		// launch the processor with the previously defined configuration
		try
    {
      Launcher.launch(launchConfiguration);
    }
    catch (IOException e)
    {
    	throw new ExceptionInInitializerError(
    			"failure launching configuration\n" + e);
    }
		
		LOGGER.debug(
				"number of type elements with @Test annotated method(s): " +
				testProcessor.typeElements.size());

		// return the type elements that were collected by the processor
		return testProcessor.typeElements;
	}

	public static void main(String[] args)
  {
		process(getSourceRoot());
  }

	/**
	 * @return root directory containing annotated source files
	 */
	private static File getSourceRoot()
	{
		File result = new File("./src/test/java");

		if (result.exists() == false)
		{
			throw new ExceptionInInitializerError(
					result.getAbsolutePath() + " does not exist");
		}
		else if (result.isDirectory() == false)
		{
			throw new ExceptionInInitializerError(
					result.getAbsolutePath() + " is not a directory");
		}

		return result;
	}
}